1
2
3
4 package edu.jiangxin.apktoolbox.android.i18n;
5
6 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
7 import edu.jiangxin.apktoolbox.swing.extend.listener.SelectDirectoryListener;
8 import edu.jiangxin.apktoolbox.utils.Constants;
9 import edu.jiangxin.apktoolbox.utils.SAXBuilderHelper;
10 import org.apache.commons.collections4.map.HashedMap;
11 import org.apache.commons.io.FileUtils;
12 import org.apache.commons.lang3.StringUtils;
13 import org.apache.commons.lang3.Strings;
14 import org.jdom2.Document;
15 import org.jdom2.Element;
16 import org.jdom2.JDOMException;
17 import org.jdom2.input.SAXBuilder;
18 import org.jdom2.output.Format;
19 import org.jdom2.output.XMLOutputter;
20
21 import javax.swing.*;
22 import java.awt.*;
23 import java.io.*;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.Map;
28
29
30
31
32
33
34 public class I18nAddPanel extends EasyPanel {
35
36 @Serial
37 private static final long serialVersionUID = 1L;
38
39 private static final String CHARSET = "UTF-8";
40
41 private static final boolean REMOVE_LAST_LF_OPEN = true;
42
43 private static final Map<String, String> replace = new HashedMap<>();
44
45 private JTextField srcTextField;
46
47 private JTextField targetTextField;
48
49 private JTextField itemTextField;
50
51 private int operationCount = 0;
52
53 static {
54 replace.put(""", "jiangxin001");
55 replace.put(" ", "jiangxin002");
56 }
57
58 public I18nAddPanel() throws HeadlessException {
59 super();
60 }
61
62 @Override
63 public void initUI() {
64 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
65 setLayout(boxLayout);
66
67 createSourcePanel();
68 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
69 createTargetPanel();
70 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
71 createItemPanel();
72 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
73 createOperationPanel();
74 }
75
76 private void createOperationPanel() {
77 JPanel operationPanel = new JPanel();
78 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
79 add(operationPanel);
80
81 JButton addButton = new JButton(bundle.getString("android.i18n.add.title"));
82 addButton.addActionListener(e -> {
83 String srcPath = checkAndGetDirContent(srcTextField, "android.i18n.add.src.dir", "Source directory is invalid");
84 if (srcPath == null) {
85 return;
86 }
87
88 String targetPath = checkAndGetDirContent(targetTextField, "android.i18n.add.target.dir", "Target directory is invalid");
89 if (targetPath == null) {
90 return;
91 }
92
93 String itemStr = checkAndGetStringContent(itemTextField, "android.i18n.add.items", "Items is empty");
94 if (itemStr == null) {
95 return;
96 }
97
98 List<String> items = new ArrayList<>(Arrays.asList(itemStr.split(";")));
99 operationCount = 0;
100
101 for (String item : items) {
102 int ret = innerProcessor(srcPath, targetPath, item);
103 if (ret != 0) {
104 Toolkit.getDefaultToolkit().beep();
105 JOptionPane.showMessageDialog(this, "Failed, please see the log", "ERROR",
106 JOptionPane.ERROR_MESSAGE);
107 return;
108 }
109 }
110 String message = String.format("Success: items: %d", operationCount);
111 JOptionPane.showMessageDialog(this, message, "INFO", JOptionPane.INFORMATION_MESSAGE);
112 });
113
114 operationPanel.add(addButton);
115 }
116
117 private void createItemPanel() {
118 JPanel itemPanel = new JPanel();
119 itemPanel.setLayout(new BoxLayout(itemPanel, BoxLayout.X_AXIS));
120 add(itemPanel);
121
122 itemTextField = new JTextField();
123 itemTextField.setText(conf.getString("android.i18n.add.items"));
124
125 JLabel itemLabel = new JLabel("Items");
126
127 itemPanel.add(itemTextField);
128 itemPanel.add(Box.createHorizontalGlue());
129 itemPanel.add(itemLabel);
130 }
131
132 private void createTargetPanel() {
133 JPanel targetPanel = new JPanel();
134 targetPanel.setLayout(new BoxLayout(targetPanel, BoxLayout.X_AXIS));
135 add(targetPanel);
136
137 targetTextField = new JTextField();
138 targetTextField.setText(conf.getString("android.i18n.add.target.dir"));
139
140 JButton targetButton = new JButton("Save Directory");
141 targetButton.addActionListener(new SelectDirectoryListener("save to", targetTextField));
142
143 targetPanel.add(targetTextField);
144 targetPanel.add(Box.createHorizontalGlue());
145 targetPanel.add(targetButton);
146 }
147
148 private void createSourcePanel() {
149 JPanel sourcePanel = new JPanel();
150 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.X_AXIS));
151 add(sourcePanel);
152
153 srcTextField = new JTextField();
154 srcTextField.setText(conf.getString("android.i18n.add.src.dir"));
155
156 JButton srcButton = new JButton("Source Directory");
157 srcButton.addActionListener(new SelectDirectoryListener("select a directory", srcTextField));
158
159 sourcePanel.add(srcTextField);
160 sourcePanel.add(Box.createHorizontalGlue());
161 sourcePanel.add(srcButton);
162 }
163
164 private int innerProcessor(String sourceBaseStr, String targetBaseStr, String itemName) {
165 if (StringUtils.isAnyEmpty(sourceBaseStr, targetBaseStr, itemName)) {
166 logger.error("params are invalid: sourceBaseStr: {}, targetBaseStr: {}, itemName: {}", sourceBaseStr, targetBaseStr, itemName);
167 return -1;
168 }
169 File sourceBaseFile = new File(sourceBaseStr);
170 File targetBaseFile = new File(targetBaseStr);
171 int count = 0;
172
173 File[] sourceParentFiles = sourceBaseFile.listFiles(new FileFilter() {
174 @Override
175 public boolean accept(File pathname) {
176 return pathname.getName().startsWith("values");
177 }
178 });
179 if (sourceParentFiles == null) {
180 logger.error("sourceParentFiles is null");
181 return -1;
182 }
183 for (File sourceParentFile : sourceParentFiles) {
184 File sourceFile = new File(sourceParentFile, "strings.xml");
185
186 Element sourceElement = getSourceElement(sourceFile, itemName);
187 if (sourceElement == null) {
188 logger.warn("sourceElement is null: {}", sourceFile);
189 continue;
190 }
191
192 File targetFile = new File(new File(targetBaseFile, sourceParentFile.getName()), "strings.xml");
193 if (!targetFile.exists()) {
194 logger.warn("targetFile does not exist: {}", sourceFile);
195 continue;
196 }
197 try {
198 preProcess(targetFile);
199 } catch (IOException e) {
200 logger.error("preProcess failed.", e);
201 return -1;
202 }
203 boolean res = setTargetElement(targetFile, sourceElement, itemName);
204 if (!res) {
205 logger.error("setTargetElement failed.");
206 return -1;
207 }
208 try {
209 postProcess(targetFile);
210 } catch (IOException e) {
211 logger.error("postProcess failed.", e);
212 return -1;
213 }
214 logger.info("count: {}, in path: {}, out path: {}", ++count, sourceFile, targetFile);
215 }
216 operationCount += count;
217 logger.info("finish one cycle");
218 return 0;
219 }
220
221 private Element getSourceElement(File sourceFile, String itemName) {
222 if (!sourceFile.exists()) {
223 logger.warn("sourceFile does not exist: {}", sourceFile);
224 return null;
225 }
226 SAXBuilder builder = new SAXBuilder();
227 SAXBuilderHelper.setSecurityFeatures(builder);
228 Document sourceDoc = null;
229 try (InputStream in = new FileInputStream(sourceFile)) {
230 sourceDoc = builder.build(in);
231 logger.info("build source document: {}", sourceFile);
232 } catch (JDOMException | IOException e) {
233 logger.error("build source document failed: {}", sourceFile);
234 return null;
235 }
236 if (sourceDoc == null) {
237 logger.error("sourceDoc is null");
238 return null;
239 }
240 Element sourceElement = null;
241 for (Element sourceChild : sourceDoc.getRootElement().getChildren()) {
242 String sourceValue = sourceChild.getAttributeValue("name");
243 if (sourceValue != null && sourceValue.equals(itemName)) {
244 sourceElement = sourceChild.clone();
245 break;
246 }
247 }
248 return sourceElement;
249 }
250
251 private boolean setTargetElement(File targetFile, Element sourceElement, String itemName) {
252 SAXBuilder builder = new SAXBuilder();
253 SAXBuilderHelper.setSecurityFeatures(builder);
254 Document targetDoc;
255 try {
256 targetDoc = builder.build(targetFile);
257 logger.info("build target document: {}", targetFile);
258 } catch (JDOMException | IOException e) {
259 logger.error("build target document failed: {}", targetFile);
260 return false;
261 }
262 Element targetRoot = targetDoc.getRootElement();
263 boolean isFinished = false;
264 for (Element targetChild : targetRoot.getChildren()) {
265 String targetValue = targetChild.getAttributeValue("name");
266 if (targetValue != null && targetValue.equals(itemName)) {
267 targetChild.setText(sourceElement.getText());
268 isFinished = true;
269 break;
270 }
271 }
272 if (!isFinished) {
273 targetRoot.addContent(" ");
274 targetRoot.addContent(sourceElement);
275 targetRoot.addContent("\n");
276 }
277 XMLOutputter out = new XMLOutputter();
278 Format format = Format.getRawFormat();
279 format.setEncoding("UTF-8");
280 format.setLineSeparator("\n");
281 out.setFormat(format);
282 OutputStream os = null;
283 try {
284 os = new FileOutputStream(targetFile);
285 out.output(targetDoc, os);
286 } catch (IOException e) {
287 logger.error("output fail", e);
288 return false;
289 } finally {
290 if (os != null) {
291 try {
292 os.close();
293 } catch (IOException e) {
294 logger.error("close output stream exception", e);
295 }
296 }
297 }
298 return true;
299 }
300
301 private static void preProcess(File file) throws IOException {
302 String content = FileUtils.readFileToString(file, CHARSET);
303 for (Map.Entry<String, String> entry : replace.entrySet()) {
304 content = content.replaceAll(entry.getKey(), entry.getValue());
305 }
306 FileUtils.writeStringToFile(file, content, CHARSET);
307 }
308
309 private static void postProcess(File file) throws IOException {
310 String content = FileUtils.readFileToString(file, CHARSET);
311 for (Map.Entry<String, String> entry : replace.entrySet()) {
312 content = content.replaceAll(entry.getValue(), entry.getKey());
313 }
314 if (REMOVE_LAST_LF_OPEN) {
315 content = Strings.CS.removeEnd(content, "\n");
316 }
317 FileUtils.writeStringToFile(file, content, CHARSET);
318 }
319
320 }